package lzw.codec.managers;

import java.io.IOException;
import java.io.StringReader;
import java.util.Map;

import lzw.codec.exceptions.DecodingException;
import lzw.codec.exceptions.EncodingException;
import lzw.codec.exceptions.SystemException;
import lzw.codec.util.Util;

/**
 * This class is an implementation of LZWCodecManager.
 * 
 * @see lzw.codec.managers.LZWCodecManager
 * 
 * @authors Bruno Cartaxo;
 * 			Edson Jordao;
 * 
 * @since SEP 2009
 */
public class LZWCodecManagerImpl extends LZWCodecManager {

	/**
	 * The singleton shared instance.
	 */
	private static LZWCodecManagerImpl instance = new LZWCodecManagerImpl();

	/**
	 * The hidden constructor.
	 */
	private LZWCodecManagerImpl(){
		// Empty constructor
	}

	/**
	 * A method to return the shared singleton instance.
	 * @return The shared singleton instance.
	 */
	public static LZWCodecManager getInsntace() {
		return instance;
	}

	/**
	 * @see lzw.codec.managers.LZWCodecManager#encode(String, Map)
	 * @author Bruno Cartaxo
	 */
	public String encode(String source, Map<Integer, String> initialDictionary)  throws EncodingException {
		StringBuilder encoded = new StringBuilder();

		if (Util.isNullOrEmpty(source)) {
			throw new EncodingException("Sequência de caracteres inválida.");
		}

		if (Util.isNullOrEmpty(initialDictionary)) {
			throw new EncodingException("Dicionário inicial inválido.");
		}

		try {
			String prefix = "";
			for (int i = 0; i < source.length(); i++) {
				char currentChar = source.charAt(i);
				if (Util.containsValue(prefix + currentChar, initialDictionary)) {
					prefix += currentChar;
				} else {
					Integer codeWord = Util.getKey(prefix, initialDictionary);
					if (codeWord == null) {
						throw new EncodingException("Sequência de caracteres inválida.");
					}
					encoded.append(codeWord);
					int nextCodeWord = Util.maxKey(initialDictionary) + 1;
					initialDictionary.put(nextCodeWord, prefix + currentChar);
					prefix = String.valueOf(currentChar);
				}
			}

			Integer lastCodeWord = Util.getKey(prefix, initialDictionary);
			encoded.append(lastCodeWord);

			if (lastCodeWord == null) {
				throw new EncodingException("Sequência de caracteres inválida.");
			}

		} catch (EncodingException exception) {
			throw exception;
		} catch (Exception exception) {
			throw new SystemException(exception.getMessage(), exception);
		}

		return encoded.toString();
	}

	/**
	 * @see lzw.codec.managers.LZWCodecManager#decode(String, Map)
	 * @author Edson Jordão
	 */
	public String decode(String encoded, Map<Integer, String> initialDictionary) throws DecodingException {

		if (Util.isNullOrEmpty(encoded)) {
			throw new DecodingException("Sequência de caracteres inválida.");
		}

		if (Util.isNullOrEmpty(initialDictionary)) {
			throw new DecodingException("Dicionário inicial inválido.");
		}

		StringReader encodedReader = new StringReader(encoded);
		StringBuffer decode = new StringBuffer();
		int cW = 0;
		int pW = 0;
		String P = "";
		String C = "";
		String PC = "";
		try {
			int finished = encodedReader.read();
			cW = Integer.parseInt(String.valueOf((char) finished));
			if(initialDictionary.get(cW) != null){
				decode.append(initialDictionary.get(cW));
			}else{
				throw new DecodingException("Sequência de caracteres inválida");
			}
			while((finished = encodedReader.read()) != -1){
				pW = cW;
				cW = Integer.parseInt(String.valueOf((char) finished));
				if(initialDictionary.containsKey(cW)){
					if(initialDictionary.get(cW) != null){
						decode.append(initialDictionary.get(cW));
					}else{
						throw new DecodingException("Sequência de caracteres inválida");
					}
					P = initialDictionary.get(pW);
					C = (initialDictionary.get(cW)).substring(0, 1);
					PC = P+C;
					initialDictionary.put(initialDictionary.size()+1, PC);
				}else{
					if(initialDictionary.get(pW) != null){
						P = initialDictionary.get(pW);
						C = (initialDictionary.get(pW)).substring(0, 1);
						PC = P+C;
					}else{
						throw new DecodingException("Sequência de caracteres inválida");
					}
					decode.append(PC);
					initialDictionary.put(initialDictionary.size()+1, PC);
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception exception) {
			throw new SystemException(exception.getMessage(), exception);
		}

		return decode.toString();
	}

}
